﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml;


namespace PServiceBus.Serializer.Xml {
    public static partial class Generator {

        public static Type Generate(Type objType) {
            var returnType = default(Type);
            if (_types.TryGetValue(objType, out returnType))
                return returnType;

            var isPrimitive = objType.IsPrimitiveType();
            var genericType = _serializerType.MakeGenericType(objType);
            var objTypeName = objType.GetName();
            var objTypeNameFix = objTypeName.Fix();
            var typeName = String.Concat(objTypeNameFix, XmlSerializerStr);
            var asmName = String.Concat(typeName, ClassStr);
            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
                new AssemblyName(asmName) { 
                    Version = new Version(1, 0, 0, 0) },
                AssemblyBuilderAccess.RunAndSave);
            var module = assembly.DefineDynamicModule(String.Concat(typeName, _dllStr));
            var type = module.DefineType(typeName, TypeAttribute, _objectType, new[] { genericType });

            var serializeMethod = type.DefineMethod(XmlSerializeStr, MethodAttribute,
                _stringType, new[] { objType });
            var deserializeMethod = type.DefineMethod(XmlDeserializeStr, MethodAttribute, objType,
                _stringParamTypes);

            var serializeIL = serializeMethod.GetILGenerator();
            var deserializeIL = deserializeMethod.GetILGenerator();

            //Serialize
            var sbLocal = serializeIL.DeclareLocal(_stringBuilderType);
            serializeIL.Emit(OpCodes.Call, _generatorGetStringBuilder);
            serializeIL.Emit(OpCodes.Callvirt, _stringBuilderClear);
            serializeIL.Emit(OpCodes.Stloc, sbLocal.LocalIndex);

            if (isPrimitive) {
                serializeIL.Emit(OpCodes.Ldloc, sbLocal.LocalIndex);
                serializeIL.Emit(OpCodes.Ldstr,
                    String.Concat(Less, objTypeNameFix, Greater));
                serializeIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                serializeIL.Emit(OpCodes.Pop);

                serializeIL.Emit(OpCodes.Ldloc, sbLocal.LocalIndex);
                serializeIL.Emit(OpCodes.Ldarg_1);
                if (objType.IsValueType)
                    serializeIL.Emit(OpCodes.Box, objType);
                serializeIL.Emit(OpCodes.Call, _generatorGetWriteString);
                serializeIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                serializeIL.Emit(OpCodes.Pop);

                serializeIL.Emit(OpCodes.Ldloc, sbLocal.LocalIndex);
                serializeIL.Emit(OpCodes.Ldstr,
                    String.Concat(CloseTag, objTypeNameFix, Greater));
                serializeIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                serializeIL.Emit(OpCodes.Pop);
            } else {
                serializeIL.Emit(OpCodes.Ldarg_0);
                serializeIL.Emit(OpCodes.Ldarg_1);
                serializeIL.Emit(OpCodes.Ldloc, sbLocal.LocalIndex);
                serializeIL.Emit(OpCodes.Callvirt, WriteSerializeMethodFor(type, objType));
            }

            serializeIL.Emit(OpCodes.Ldloc, sbLocal.LocalIndex);
            serializeIL.Emit(OpCodes.Callvirt, _stringBuilderToString);
            serializeIL.Emit(OpCodes.Ret);


            //Deserialize
            var readerLocal = deserializeIL.DeclareLocal(_xmlReaderExType);

            deserializeIL.Emit(OpCodes.Ldarg_1);
            deserializeIL.Emit(OpCodes.Newobj, _xmlReaderExCtor);
            deserializeIL.Emit(OpCodes.Stloc, readerLocal.LocalIndex);

            deserializeIL.Emit(OpCodes.Ldloc, readerLocal.LocalIndex);
            deserializeIL.Emit(OpCodes.Callvirt, _xmlReaderExRead);
            deserializeIL.Emit(OpCodes.Pop);

            if (!isPrimitive) deserializeIL.Emit(OpCodes.Ldarg_0);
            deserializeIL.Emit(OpCodes.Ldloc, readerLocal.LocalIndex);
            deserializeIL.Emit(isPrimitive ? OpCodes.Call : OpCodes.Callvirt,
                WriteDeserializeMethodFor(type, objType));
            deserializeIL.Emit(OpCodes.Ret);


            type.DefineMethodOverride(serializeMethod,
                genericType.GetMethod(SerializeStr));
            type.DefineMethodOverride(deserializeMethod,
                genericType.GetMethod(DeserializeStr));

            returnType = type.CreateType();
            _types[objType] = returnType;
            //assembly.Save(String.Concat(typeName, _dllStr));

            return returnType;
        }

        internal static MethodInfo WriteSerializeMethodFor(TypeBuilder typeBuilder, Type type, string tagName = null) {
            if (type.IsPrimitiveType()) return _generatorWriteObject;
            MethodBuilder method;
            var key = type.FullName;
            var typeName = type.GetName();
            typeName = typeName.Fix();
            if (_writeMethodBuilders.TryGetValue(key, out method))
                return method;
            var methodName = String.Concat(WriteStr, typeName);
            method = typeBuilder.DefineMethod(methodName, MethodAttribute,
                _voidType, new []{ type, _stringBuilderType });
            _writeMethodBuilders[key] = method;
            var methodIL = method.GetILGenerator();
            WriteSerializeFor(typeBuilder, type, tagName ?? typeName, methodIL);
            methodIL.Emit(OpCodes.Ret);
            return method;
        }

        internal static MethodInfo WriteDeserializeMethodFor(TypeBuilder typeBuilder, Type type) {
            MethodInfo method;
            var key = type.FullName;
            if (_readMethodBuilders.TryGetValue(key, out method))
                return method;

            if (type.IsPrimitiveType()) {
                method = _readMethodBuilders[key] =
                    _generatorReadObject.MakeGenericMethod(type);
                return method;
            }
            var typeName = type.GetName();
            typeName = typeName.Fix();
            var methodName = String.Concat(ReadStr, typeName);
            method = typeBuilder.DefineMethod(methodName, MethodAttribute,
                type, new[] { _xmlReaderExType });
            _readMethodBuilders[key] = method;
            var methodIL = (method as MethodBuilder).GetILGenerator();
            WriteDeserializeFor(typeBuilder, type, methodIL);
            methodIL.Emit(OpCodes.Ret);
            return method;
        }

        internal static void ReadPropertiesFor(TypeBuilder typeBuilder, Type type, LocalBuilder valueLocal, LocalBuilder nodeNameLocal, ILGenerator methodIL) {
            var isClass = type.IsClass;
            var isNodeEndLabel = methodIL.DefineLabel();
            var breakLabel = methodIL.DefineLabel();
            var isNodeValidLabel = methodIL.DefineLabel();
            var readLabel = methodIL.DefineLabel();
            var whileLabel = methodIL.DefineLabel();
            var readLocal = methodIL.DeclareLocal(_boolType);
            var method = WriteReadPropertyMethodFor(typeBuilder, type);

            methodIL.Emit(OpCodes.Br, whileLabel);
            methodIL.MarkLabel(readLabel);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldloc, nodeNameLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call, _generatorIsNodeValid);
            methodIL.Emit(OpCodes.Ldc_I4_0);
            methodIL.Emit(OpCodes.Ceq);
            methodIL.Emit(OpCodes.Brtrue, isNodeValidLabel);

            methodIL.Emit(OpCodes.Ldarg_0);
            methodIL.Emit(isClass ? OpCodes.Ldloc : OpCodes.Ldloca, valueLocal);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, method);

            methodIL.MarkLabel(isNodeValidLabel);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldloc, nodeNameLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call, _generatorIsNodeEnd);
            methodIL.Emit(OpCodes.Ldc_I4_0);
            methodIL.Emit(OpCodes.Ceq);
            methodIL.Emit(OpCodes.Brtrue, isNodeEndLabel);

            methodIL.Emit(OpCodes.Br, breakLabel);
            methodIL.MarkLabel(isNodeEndLabel);

            methodIL.MarkLabel(whileLabel);
            methodIL.Emit(OpCodes.Ldc_I4_1);
            methodIL.Emit(OpCodes.Stloc, readLocal.LocalIndex);

            methodIL.Emit(OpCodes.Br, readLabel);
            methodIL.MarkLabel(breakLabel);
        }

        internal static void CreateReadProperty(TypeBuilder typeBuilder, Type type, ILGenerator methodIL) {
            var props = type.GetTypeProperties();
            var isClass = type.IsClass;
            var nodeNameLocal = methodIL.DeclareLocal(_stringType);

            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExElementName);
            methodIL.Emit(OpCodes.Stloc, nodeNameLocal.LocalIndex);

            foreach (var prop in props) {
                var name = prop.Name;
                var propType = prop.PropertyType;
                var isPrimitive = propType.IsPrimitiveType();
                var propLabel = methodIL.DefineLabel();
                var method = WriteDeserializeMethodFor(typeBuilder, propType);

                methodIL.Emit(OpCodes.Ldloc, nodeNameLocal.LocalIndex);
                methodIL.Emit(OpCodes.Ldstr, name);
                methodIL.Emit(OpCodes.Call, _stringOpEquality);
                methodIL.Emit(OpCodes.Brfalse, propLabel);
                
                if (isClass) methodIL.Emit(OpCodes.Ldarg_1);
                else methodIL.Emit(OpCodes.Ldarga, 1);
                if (!isPrimitive) methodIL.Emit(OpCodes.Ldarg_0);
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(isPrimitive ? OpCodes.Call : OpCodes.Callvirt, method);
                methodIL.Emit(isClass ? OpCodes.Callvirt : OpCodes.Call, prop.GetSetMethod());

                methodIL.Emit(OpCodes.Ret);
                methodIL.MarkLabel(propLabel);
            }

            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldloc, nodeNameLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call, _generatorReadToEndElement);
        }

        internal static MethodInfo WriteReadPropertyMethodFor(TypeBuilder typeBuilder, Type type) {
            MethodInfo method;
            var key = type.FullName;
            if (_readPropertyMethodBuilders.TryGetValue(key, out method)) return method;
            var typeName = type.GetName();
            typeName = typeName.Fix();
            var methodName = String.Concat(ReadPropertyStr, typeName);
            method = typeBuilder.DefineMethod(methodName, MethodAttribute,
                _voidType, new[] { type.IsClass ? type : type.MakeByRefType(), _xmlReaderExType });
            _readPropertyMethodBuilders[key] = method;
            var methodIL = (method as MethodBuilder).GetILGenerator();
            CreateReadProperty(typeBuilder, type, methodIL);
            methodIL.Emit(OpCodes.Ret);
            return method;
        }
        
        internal static void WritePropertiesFor(TypeBuilder typeBuilder, Type type, string typeName, ILGenerator methodIL) {
            
            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr, 
                String.Concat(Less, typeName, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);

            var props = type.GetTypeProperties();
            foreach (var prop in props) {
                var name = prop.Name;
                var propType = prop.PropertyType;
                var isPrimitive = propType.IsPrimitiveType();
                
                if (isPrimitive) {
                    methodIL.Emit(OpCodes.Ldarg_2);

                    if (propType == _stringType) 
                        methodIL.Emit(OpCodes.Ldstr, String.Concat(Less, name, Space, TypeStr, Equal, Quote, StrStr, Quote, Greater));
                    else
                        methodIL.Emit(OpCodes.Ldstr,
                            String.Concat(Less, name, Greater));

                    methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                    methodIL.Emit(OpCodes.Pop);

                    methodIL.Emit(OpCodes.Ldarg_2);
                    methodIL.Emit(OpCodes.Ldarg_1);
                    methodIL.Emit(OpCodes.Callvirt, prop.GetGetMethod());
                    if (propType.IsValueType)
                        methodIL.Emit(OpCodes.Box, propType);
                    methodIL.Emit(OpCodes.Call, _generatorGetWriteString);
                    methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                    methodIL.Emit(OpCodes.Pop);

                    methodIL.Emit(OpCodes.Ldarg_2);
                    methodIL.Emit(OpCodes.Ldstr,
                        String.Concat(CloseTag, name, Greater));
                    methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                    methodIL.Emit(OpCodes.Pop);
                } else {
                    methodIL.Emit(OpCodes.Ldarg_0);
                    methodIL.Emit(OpCodes.Ldarg_1);
                    methodIL.Emit(OpCodes.Callvirt, prop.GetGetMethod());
                    methodIL.Emit(OpCodes.Ldarg_2);
                    methodIL.Emit(OpCodes.Callvirt, WriteSerializeMethodFor(typeBuilder, propType, tagName: name));
                }
            }

            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(CloseTag, typeName, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);
        }

        internal static void WriteDictionary(TypeBuilder typeBuilder, Type type, ILGenerator methodIL) {
            var arguments = type.GetGenericArguments();
            var keyType = arguments[0];
            var valueType = arguments[1];
            var isKeyPrimitive = keyType.IsPrimitiveType();
            var isValuePrimitive = valueType.IsPrimitiveType();
            var keyValuePairType = _genericKeyValuePairType.MakeGenericType(keyType, valueType);
            var enumeratorType = _genericDictionaryEnumerator.MakeGenericType(keyType, valueType);
            var enumeratorLocal = methodIL.DeclareLocal(enumeratorType);
            var entryLocal = methodIL.DeclareLocal(keyValuePairType);
            var startEnumeratorLabel = methodIL.DefineLabel();
            var moveNextLabel = methodIL.DefineLabel();
            var endEnumeratorLabel = methodIL.DefineLabel();

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt,
                _genericDictType.MakeGenericType(keyType, valueType).GetMethod("GetEnumerator"));
            methodIL.Emit(OpCodes.Stloc_S, enumeratorLocal.LocalIndex);
            methodIL.BeginExceptionBlock();
            methodIL.Emit(OpCodes.Br, startEnumeratorLabel);
            methodIL.MarkLabel(moveNextLabel);
            methodIL.Emit(OpCodes.Ldloca_S, enumeratorLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call,
                enumeratorLocal.LocalType.GetProperty("Current")
                .GetGetMethod());
            methodIL.Emit(OpCodes.Stloc, entryLocal.LocalIndex);


            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(Less, ItemStr, Space, TypeStr, Equal,
                Quote, DictStr, Quote, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);


            if (isKeyPrimitive) {
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(Less, KeyStr, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldloca_S, entryLocal.LocalIndex);
                methodIL.Emit(OpCodes.Call, keyValuePairType.GetProperty("Key").GetGetMethod());
                if (keyType.IsValueType)
                    methodIL.Emit(OpCodes.Box, keyType);
                methodIL.Emit(OpCodes.Call, _generatorGetWriteString);
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(CloseTag, KeyStr, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);
            } else {
                methodIL.Emit(OpCodes.Ldarg_0);
                methodIL.Emit(OpCodes.Ldloca_S, entryLocal.LocalIndex);
                methodIL.Emit(OpCodes.Call, keyValuePairType.GetProperty("Key").GetGetMethod());
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Callvirt, WriteSerializeMethodFor(typeBuilder, keyType));
            }



            if (isValuePrimitive) {
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(Less, ValueStr, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldloca_S, entryLocal.LocalIndex);
                methodIL.Emit(OpCodes.Call, keyValuePairType.GetProperty("Value").GetGetMethod());
                if (valueType.IsValueType)
                    methodIL.Emit(OpCodes.Box, valueType);
                methodIL.Emit(OpCodes.Call, _generatorGetWriteString);
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(CloseTag, ValueStr, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);
            } else {
                methodIL.Emit(OpCodes.Ldarg_0);
                methodIL.Emit(OpCodes.Ldloca_S, entryLocal.LocalIndex);
                methodIL.Emit(OpCodes.Call, keyValuePairType.GetProperty("Value").GetGetMethod());
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Callvirt, WriteSerializeMethodFor(typeBuilder, valueType));
            }


            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(CloseTag, ItemStr, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);

            methodIL.MarkLabel(startEnumeratorLabel);
            methodIL.Emit(OpCodes.Ldloca_S, enumeratorLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call, enumeratorType.GetMethod("MoveNext", MethodBinding));
            methodIL.Emit(OpCodes.Brtrue, moveNextLabel);
            methodIL.Emit(OpCodes.Leave, endEnumeratorLabel);
            methodIL.BeginFinallyBlock();
            methodIL.Emit(OpCodes.Ldloca_S, enumeratorLocal.LocalIndex);
            methodIL.Emit(OpCodes.Constrained, enumeratorLocal.LocalType);
            methodIL.Emit(OpCodes.Callvirt, _iDisposableDispose);
            methodIL.EndExceptionBlock();
            methodIL.MarkLabel(endEnumeratorLabel);
        }

        internal static void WriteArray(TypeBuilder typeBuilder, Type type, ILGenerator methodIL) {
            var itemType = type.GetElementType();
            var name = itemType.Name.Fix();
            var isPrimitive = itemType.IsPrimitiveType();
            var itemLocal = methodIL.DeclareLocal(itemType);
            var indexLocal = methodIL.DeclareLocal(_intType);
            var startLabel = methodIL.DefineLabel();
            var endLabel = methodIL.DefineLabel();


            methodIL.Emit(OpCodes.Ldc_I4_0);
            methodIL.Emit(OpCodes.Stloc_S, indexLocal.LocalIndex);
            methodIL.Emit(OpCodes.Br, startLabel);
            methodIL.MarkLabel(endLabel);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldloc_S, indexLocal.LocalIndex);
            methodIL.Emit(OpCodes.Ldelem, itemType);
            methodIL.Emit(OpCodes.Stloc, itemLocal.LocalIndex);


            if (isPrimitive) {
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(Less, name, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldloc, itemLocal.LocalIndex);
                if (itemType.IsValueType)
                    methodIL.Emit(OpCodes.Box, itemType);
                methodIL.Emit(OpCodes.Call, _generatorGetWriteString);
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(CloseTag, name, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);
            } else {
                methodIL.Emit(OpCodes.Ldarg_0);
                methodIL.Emit(OpCodes.Ldloc, itemLocal.LocalIndex);
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Callvirt, WriteSerializeMethodFor(typeBuilder, itemType));
            }


            methodIL.Emit(OpCodes.Ldloc_S, indexLocal.LocalIndex);
            methodIL.Emit(OpCodes.Ldc_I4_1);
            methodIL.Emit(OpCodes.Add);
            methodIL.Emit(OpCodes.Stloc_S, indexLocal.LocalIndex);
            methodIL.MarkLabel(startLabel);
            methodIL.Emit(OpCodes.Ldloc_S, indexLocal.LocalIndex);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldlen);
            methodIL.Emit(OpCodes.Conv_I4);
            methodIL.Emit(OpCodes.Blt, endLabel);
        }

        internal static void ReadListArray(TypeBuilder typeBuilder, Type type, LocalBuilder valueLocal, ILGenerator methodIL) {
            var isArray = type.IsArray;
            var itemType = isArray ? type.GetElementType()
                : type.GetGenericArguments()[0];
            var isClass = itemType.IsClass;
            var isPrimitive = itemType.IsPrimitiveType();
            var listType = _genericListType.MakeGenericType(itemType);
            var readLocal = methodIL.DeclareLocal(_boolType);
            var listLocal = isArray ? methodIL.DeclareLocal(listType) : valueLocal;
            var isNodeEndLabel = methodIL.DefineLabel();
            var breakLabel = methodIL.DefineLabel();
            var isNodeValidLabel = methodIL.DefineLabel();
            var readLabel = methodIL.DefineLabel();
            var whileLabel = methodIL.DefineLabel();

            methodIL.Emit(OpCodes.Newobj, listType.GetConstructor(Type.EmptyTypes));
            methodIL.Emit(OpCodes.Stloc, listLocal.LocalIndex);

            methodIL.Emit(OpCodes.Br, whileLabel);
            methodIL.MarkLabel(readLabel);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldstr, ItemsStr);
            methodIL.Emit(OpCodes.Call, _generatorIsNodeValid);
            methodIL.Emit(OpCodes.Ldc_I4_0);
            methodIL.Emit(OpCodes.Ceq);
            methodIL.Emit(OpCodes.Brtrue, isNodeValidLabel);

            //Process Item
            methodIL.Emit(OpCodes.Ldloc, listLocal.LocalIndex);
            if (!isPrimitive) methodIL.Emit(OpCodes.Ldarg_0);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(isPrimitive ? OpCodes.Call : OpCodes.Callvirt,
                WriteDeserializeMethodFor(typeBuilder, itemType));
            methodIL.Emit(OpCodes.Callvirt, listType.GetMethod(AddStr));
            //End Process Item

            methodIL.MarkLabel(isNodeValidLabel);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldstr, ItemsStr);
            methodIL.Emit(OpCodes.Call, _generatorIsNodeEnd);
            methodIL.Emit(OpCodes.Ldc_I4_0);
            methodIL.Emit(OpCodes.Ceq);
            methodIL.Emit(OpCodes.Brtrue, isNodeEndLabel);

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExRead);
            methodIL.Emit(OpCodes.Pop);
            methodIL.Emit(OpCodes.Br, breakLabel);
            methodIL.MarkLabel(isNodeEndLabel);

            methodIL.MarkLabel(whileLabel);
            methodIL.Emit(OpCodes.Ldc_I4_1);
            methodIL.Emit(OpCodes.Stloc, readLocal.LocalIndex);
            
            methodIL.Emit(OpCodes.Br, readLabel);
            methodIL.MarkLabel(breakLabel);

            if (isArray) {
                methodIL.Emit(OpCodes.Ldloc, listLocal.LocalIndex);
                methodIL.Emit(OpCodes.Callvirt,
                    listType.GetMethod(ToArrayStr));
                methodIL.Emit(OpCodes.Stloc, valueLocal.LocalIndex);
            }
        }

        internal static void ReadDictionary(TypeBuilder typeBuilder, Type type, LocalBuilder valueLocal, ILGenerator methodIL) {
            var arguments = type.GetGenericArguments();
            var keyType = arguments[0];
            var isKeyClass = keyType.IsClass;
            var isKeyPrimitive = keyType.IsPrimitiveType();
            var valueType = arguments[1];
            var isValueClass = valueType.IsClass;
            var isValuePrimitive = valueType.IsPrimitiveType();
            var dictType = _genericDictType.MakeGenericType(keyType, valueType);
            var readLocal = methodIL.DeclareLocal(_boolType);
            var isNodeEndLabel = methodIL.DefineLabel();
            var breakLabel = methodIL.DefineLabel();
            var isNodeValidLabel = methodIL.DefineLabel();
            var readLabel = methodIL.DefineLabel();
            var whileLabel = methodIL.DefineLabel();

            methodIL.Emit(OpCodes.Newobj, dictType.GetConstructor(Type.EmptyTypes));
            methodIL.Emit(OpCodes.Stloc, valueLocal.LocalIndex);


            methodIL.Emit(OpCodes.Br, whileLabel);
            methodIL.MarkLabel(readLabel);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldstr, ItemsStr);
            methodIL.Emit(OpCodes.Call, _generatorIsNodeValid);
            methodIL.Emit(OpCodes.Ldc_I4_0);
            methodIL.Emit(OpCodes.Ceq);
            methodIL.Emit(OpCodes.Brtrue, isNodeValidLabel);

            //Process Item
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExRead);
            methodIL.Emit(OpCodes.Pop);

            methodIL.Emit(OpCodes.Ldloc, valueLocal.LocalIndex);

            if (!isKeyPrimitive) methodIL.Emit(OpCodes.Ldarg_0);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(isKeyPrimitive ? OpCodes.Call : OpCodes.Callvirt,
                WriteDeserializeMethodFor(typeBuilder, keyType));

            if (!isValuePrimitive) methodIL.Emit(OpCodes.Ldarg_0);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(isValuePrimitive ? OpCodes.Call : OpCodes.Callvirt,
                WriteDeserializeMethodFor(typeBuilder, valueType));

            methodIL.Emit(OpCodes.Callvirt, dictType.GetMethod(SetItemStr));

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExRead);
            methodIL.Emit(OpCodes.Pop);
            //End Process Item

            methodIL.MarkLabel(isNodeValidLabel);
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldstr, ItemsStr);
            methodIL.Emit(OpCodes.Call, _generatorIsNodeEnd);
            methodIL.Emit(OpCodes.Ldc_I4_0);
            methodIL.Emit(OpCodes.Ceq);
            methodIL.Emit(OpCodes.Brtrue, isNodeEndLabel);

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExRead);
            methodIL.Emit(OpCodes.Pop);
            methodIL.Emit(OpCodes.Br, breakLabel);
            methodIL.MarkLabel(isNodeEndLabel);

            methodIL.MarkLabel(whileLabel);
            methodIL.Emit(OpCodes.Ldc_I4_1);
            methodIL.Emit(OpCodes.Stloc, readLocal.LocalIndex);

            methodIL.Emit(OpCodes.Br, readLabel);
            methodIL.MarkLabel(breakLabel);
        }

        internal static void ReadCollection(TypeBuilder typeBuilder, Type type, LocalBuilder valueLocal, ILGenerator methodIL) {
            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExRead);
            methodIL.Emit(OpCodes.Pop);

            if (type.IsDictionaryType()) 
                ReadDictionary(typeBuilder, type, valueLocal, methodIL);
            else ReadListArray(typeBuilder, type, valueLocal, methodIL);
        }

        internal static void WriteList(TypeBuilder typeBuilder, Type type, ILGenerator methodIL) {
            var arguments = type.GetGenericArguments();
            var listType = arguments.Length > 0 ? arguments[0] : _objectType;
            var name = listType.Name.Fix();
            var isPrimitive = listType.IsPrimitiveType();
            var genericListType = _genericListType.MakeGenericType(listType);
            var enumeratorType = _genericListEnumerator.MakeGenericType(listType);
            var enumeratorLocal = methodIL.DeclareLocal(enumeratorType);
            var entryLocal = methodIL.DeclareLocal(listType);
            var startEnumeratorLabel = methodIL.DefineLabel();
            var moveNextLabel = methodIL.DefineLabel();
            var endEnumeratorLabel = methodIL.DefineLabel();


            methodIL.Emit(OpCodes.Ldarg_1);
            if (type.Name == IListStr) methodIL.Emit(OpCodes.Castclass, genericListType);
            methodIL.Emit(OpCodes.Callvirt, 
                genericListType.GetMethod("GetEnumerator"));
            methodIL.Emit(OpCodes.Stloc_S, enumeratorLocal.LocalIndex);
            methodIL.BeginExceptionBlock();
            methodIL.Emit(OpCodes.Br, startEnumeratorLabel);
            methodIL.MarkLabel(moveNextLabel);
            methodIL.Emit(OpCodes.Ldloca_S, enumeratorLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call,
                enumeratorLocal.LocalType.GetProperty("Current")
                .GetGetMethod());
            methodIL.Emit(OpCodes.Stloc, entryLocal.LocalIndex);


            if (isPrimitive) {
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(Less, name, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldloc, entryLocal.LocalIndex);
                if (listType.IsValueType)
                    methodIL.Emit(OpCodes.Box, listType);
                methodIL.Emit(OpCodes.Call, _generatorGetWriteString);
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);

                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Ldstr,
                    String.Concat(CloseTag, name, Greater));
                methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
                methodIL.Emit(OpCodes.Pop);
            } else {
                methodIL.Emit(OpCodes.Ldarg_0);
                methodIL.Emit(OpCodes.Ldloc, entryLocal.LocalIndex);
                methodIL.Emit(OpCodes.Ldarg_2);
                methodIL.Emit(OpCodes.Callvirt, WriteSerializeMethodFor(typeBuilder, listType));
            }


            methodIL.MarkLabel(startEnumeratorLabel);
            methodIL.Emit(OpCodes.Ldloca_S, enumeratorLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call, enumeratorType.GetMethod("MoveNext", MethodBinding));
            methodIL.Emit(OpCodes.Brtrue, moveNextLabel);
            methodIL.Emit(OpCodes.Leave, endEnumeratorLabel);
            methodIL.BeginFinallyBlock();
            methodIL.Emit(OpCodes.Ldloca_S, enumeratorLocal.LocalIndex);
            methodIL.Emit(OpCodes.Constrained, enumeratorLocal.LocalType);
            methodIL.Emit(OpCodes.Callvirt, _iDisposableDispose);
            methodIL.EndExceptionBlock();
            methodIL.MarkLabel(endEnumeratorLabel);
        }

        internal static void WriteListArray(TypeBuilder typeBuilder, Type type, ILGenerator methodIL) {
            if (type.IsArray) WriteArray(typeBuilder, type, methodIL);
            else WriteList(typeBuilder, type, methodIL);
        }

        internal static void WriteCollection(TypeBuilder typeBuilder, Type type, string typeName, ILGenerator methodIL) {
            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(Less, typeName, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);


            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(Less, ItemsStr, Space, TypeStr, Equal,
                Quote, ItemsStr, Quote, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);


            if (type.IsDictionaryType())
                WriteDictionary(typeBuilder, type, methodIL);
            else WriteListArray(typeBuilder, type, methodIL);

            
            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(CloseTag, ItemsStr, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);


            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(CloseTag, typeName, Greater));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);
        }

        internal static void WriteDeserializeFor(TypeBuilder typeBuilder, Type type, ILGenerator methodIL) {
            var valueLocal = methodIL.DeclareLocal(type);
            var nodeNameLocal = methodIL.DeclareLocal(_stringType);
            var conditionLocal = methodIL.DefineLabel();
            var isClass = type.IsClass;

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Call, _generatorHasNilAttribute);
            methodIL.Emit(OpCodes.Brfalse_S, conditionLocal);
            methodIL.Emit(OpCodes.Ldnull);
            methodIL.Emit(OpCodes.Ret);
            methodIL.MarkLabel(conditionLocal);

            if (isClass) {
                if (!(type.IsDictionaryType() || type.IsListType() || type.IsArray)) {
                    methodIL.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
                    methodIL.Emit(OpCodes.Stloc, valueLocal.LocalIndex);
                }
            } else {
                methodIL.Emit(OpCodes.Ldloca, valueLocal.LocalIndex);
                methodIL.Emit(OpCodes.Initobj, type);
            }

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExElementName);
            methodIL.Emit(OpCodes.Stloc, nodeNameLocal.LocalIndex);

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Callvirt, _xmlReaderExRead);
            methodIL.Emit(OpCodes.Pop);

            if (type.IsClassType())
                ReadPropertiesFor(typeBuilder, type, valueLocal, nodeNameLocal, methodIL);
            else
                ReadCollection(typeBuilder, type, valueLocal, methodIL);

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Ldloc, nodeNameLocal.LocalIndex);
            methodIL.Emit(OpCodes.Call, _generatorReadToEndElement);

            methodIL.Emit(isClass ? OpCodes.Ldloc : OpCodes.Ldloca,
                valueLocal.LocalIndex);
        }

        internal static void WriteSerializeFor(TypeBuilder typeBuilder, Type type, string typeName, ILGenerator methodIL) {
            var conditionLabel = methodIL.DefineLabel();

            methodIL.Emit(OpCodes.Ldarg_1);
            methodIL.Emit(OpCodes.Brtrue, conditionLabel);
            methodIL.Emit(OpCodes.Ldarg_2);
            methodIL.Emit(OpCodes.Ldstr,
                String.Concat(Less, typeName, Space, TypeStr, Equal,
                Quote, NilStr, Quote, Space, EndTag));
            methodIL.Emit(OpCodes.Callvirt, _stringBuilderAppend);
            methodIL.Emit(OpCodes.Pop);
            methodIL.Emit(OpCodes.Ret);
            methodIL.MarkLabel(conditionLabel);

            if (type.IsClassType()) WritePropertiesFor(typeBuilder, type, typeName, methodIL);
            else WriteCollection(typeBuilder, type, typeName, methodIL);
        }
    }
}
